#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <string>
#include <GL/glew.h>

#include <glfw3.h>

#include "texture.h"
#include <stb_image.h>

GLuint loadBMP_custom(const char * imagepath) {

	printf("Reading image %s\n", imagepath);

	// Data read from the header of the BMP file
	unsigned char header[54];
	unsigned int dataPos;
	unsigned int imageSize;
	unsigned int width, height;
	// Actual RGB data
	unsigned char * data;

	// Open the file
	FILE * file = fopen(imagepath, "rb");
	if (!file) { printf("%s could not be opened. Are you in the right directory ? Don't forget to read the FAQ !\n", imagepath); getchar(); return 0; }

	// Read the header, i.e. the 54 first bytes

	// If less than 54 bytes are read, problem
	if (fread(header, 1, 54, file) != 54) {
		printf("Not a correct BMP file\n");
		return 0;
	}
	// A BMP files always begins with "BM"
	if (header[0] != 'B' || header[1] != 'M') {
		printf("Not a correct BMP file\n");
		return 0;
	}
	// Make sure this is a 24bpp file
	if (*(int*)&(header[0x1E]) != 0) { printf("Not a correct BMP file\n");    return 0; }
	if (*(int*)&(header[0x1C]) != 24) { printf("Not a correct BMP file\n");    return 0; }

	// Read the information about the image
	dataPos = *(int*)&(header[0x0A]);
	imageSize = *(int*)&(header[0x22]);
	width = *(int*)&(header[0x12]);
	height = *(int*)&(header[0x16]);

	// Some BMP files are misformatted, guess missing information
	if (imageSize == 0)    imageSize = width*height * 3; // 3 : one byte for each Red, Green and Blue component
	if (dataPos == 0)      dataPos = 54; // The BMP header is done that way

										 // Create a buffer
	data = new unsigned char[imageSize];

	// Read the actual data from the file into the buffer
	fread(data, 1, imageSize, file);

	// Everything is in memory now, the file wan be closed
	fclose(file);

	// Create one OpenGL texture
	GLuint textureID;
	glGenTextures(1, &textureID);

	// "Bind" the newly created texture : all future texture functions will modify this texture
	glBindTexture(GL_TEXTURE_2D, textureID);

	// Give the image to OpenGL
	glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_BGR, GL_UNSIGNED_BYTE, data);

	// OpenGL has now copied the data. Free our own version
	delete[] data;

	// Poor filtering, or ...
	//glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
	//glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); 

	// ... nice trilinear filtering.
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
	glGenerateMipmap(GL_TEXTURE_2D);

	// Return the ID of the texture we just created
	return textureID;
}

// Since GLFW 3, glfwLoadTexture2D() has been removed. You have to use another texture loading library, 
// or do it yourself (just like loadBMP_custom and loadDDS)
//GLuint loadTGA_glfw(const char * imagepath){
//
//	// Create one OpenGL texture
//	GLuint textureID;
//	glGenTextures(1, &textureID);
//
//	// "Bind" the newly created texture : all future texture functions will modify this texture
//	glBindTexture(GL_TEXTURE_2D, textureID);
//
//	// Read the file, call glTexImage2D with the right parameters
//	glfwLoadTexture2D(imagepath, 0);
//
//	// Nice trilinear filtering.
//	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
//	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
//	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
//	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); 
//	glGenerateMipmap(GL_TEXTURE_2D);
//
//	// Return the ID of the texture we just created
//	return textureID;
//}



#define FOURCC_DXT1 0x31545844 // Equivalent to "DXT1" in ASCII
#define FOURCC_DXT3 0x33545844 // Equivalent to "DXT3" in ASCII
#define FOURCC_DXT5 0x35545844 // Equivalent to "DXT5" in ASCII

GLuint loadDDS(const char * imagepath) {

	unsigned char header[124];

	FILE *fp;

	/* try to open the file */
	fp = fopen(imagepath, "rb");
	if (fp == NULL) {
		printf("%s could not be opened. Are you in the right directory ? Don't forget to read the FAQ !\n", imagepath); getchar();
		return 0;
	}

	/* verify the type of file */
	char filecode[4];
	fread(filecode, 1, 4, fp);
	if (strncmp(filecode, "DDS ", 4) != 0) {
		fclose(fp);
		return 0;
	}

	/* get the surface desc */
	fread(&header, 124, 1, fp);

	unsigned int height = *(unsigned int*)&(header[8]);
	unsigned int width = *(unsigned int*)&(header[12]);
	unsigned int linearSize = *(unsigned int*)&(header[16]);
	unsigned int mipMapCount = *(unsigned int*)&(header[24]);
	unsigned int fourCC = *(unsigned int*)&(header[80]);


	unsigned char * buffer;
	unsigned int bufsize;
	/* how big is it going to be including all mipmaps? */
	bufsize = mipMapCount > 1 ? linearSize * 2 : linearSize;
	buffer = (unsigned char*)malloc(bufsize * sizeof(unsigned char));
	fread(buffer, 1, bufsize, fp);
	/* close the file pointer */
	fclose(fp);

	unsigned int components = (fourCC == FOURCC_DXT1) ? 3 : 4;
	unsigned int format;
	switch (fourCC)
	{
	case FOURCC_DXT1:
		format = GL_COMPRESSED_RGBA_S3TC_DXT1_EXT;
		break;
	case FOURCC_DXT3:
		format = GL_COMPRESSED_RGBA_S3TC_DXT3_EXT;
		break;
	case FOURCC_DXT5:
		format = GL_COMPRESSED_RGBA_S3TC_DXT5_EXT;
		break;
	default:
		free(buffer);
		return 0;
	}

	// Create one OpenGL texture
	GLuint textureID;
	glGenTextures(1, &textureID);

	// "Bind" the newly created texture : all future texture functions will modify this texture
	glBindTexture(GL_TEXTURE_2D, textureID);
	glPixelStorei(GL_UNPACK_ALIGNMENT, 1);

	unsigned int blockSize = (format == GL_COMPRESSED_RGBA_S3TC_DXT1_EXT) ? 8 : 16;
	unsigned int offset = 0;

	/* load the mipmaps */
	for (unsigned int level = 0; level < mipMapCount && (width || height); ++level)
	{
		unsigned int size = ((width + 3) / 4)*((height + 3) / 4)*blockSize;
		glCompressedTexImage2D(GL_TEXTURE_2D, level, format, width, height,
			0, size, buffer + offset);

		offset += size;
		width /= 2;
		height /= 2;

		// Deal with Non-Power-Of-Two textures. This code is not included in the webpage to reduce clutter.
		if (width < 1) width = 1;
		if (height < 1) height = 1;

	}

	free(buffer);

	return textureID;
}


GLuint loadTexture(char const * path)
{
	GLuint textureID;
	glGenTextures(1, &textureID);

	int width, height, nrComponents;
	unsigned char *data = stbi_load(path, &width, &height, &nrComponents, 0);
	if (data)
	{
		GLenum format;
		if (nrComponents == 1)
			format = GL_RED;
		else if (nrComponents == 3)
			format = GL_RGB;
		else if (nrComponents == 4)
			format = GL_RGBA;

		glBindTexture(GL_TEXTURE_2D, textureID);
		glTexImage2D(GL_TEXTURE_2D, 0, format, width, height, 0, format, GL_UNSIGNED_BYTE, data);
		

		glTexParameteri(GL_TEXTURE_2D, GL_GENERATE_MIPMAP, GL_TRUE);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 0);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL,  7);
		glGenerateMipmap(GL_TEXTURE_2D);

		stbi_image_free(data);
	}
	else
	{
		stbi_image_free(data);
	}

	return textureID;
}

GLuint loadDisplacementTexture(char const * path)
{
	GLuint textureID;
	glGenTextures(1, &textureID);

	int width, height, nrComponents;
	unsigned char *data = stbi_load(path, &width, &height, &nrComponents, 0);
	if (data)
	{
		GLenum format;
		if (nrComponents == 1)
			format = GL_RED;
		else if (nrComponents == 3)
			format = GL_RGB;
		else if (nrComponents == 4)
			format = GL_RGBA;

		int chans = 4;
		int ScanWidth = chans * width;
		int TheSize = ScanWidth * height;


		float iwidth = 1.0 / width;
		float iheight = 1.0 / height;

		const float max_ratio = 1.0;
		const float cone_tol2 = 4.0 / (255.0 * 255.0);

		for(int y = 0; y < height; y++)
			for (int x = 0; x < width; x++)
			{
				int der;
				if (x == 0)
					der = (data[y*ScanWidth + chans * (x + 1)] - data[y*ScanWidth + chans * x]) / 2;
				else if(x == width -1)
					der = (data[y*ScanWidth + chans * (x)] - data[y*ScanWidth + chans * (x-1)]) / 2;
				else
					der = data[y*ScanWidth + chans * (x + 1)] - data[y*ScanWidth + chans * (x - 1)];
				data[y * ScanWidth + chans * x + 2] = 127 + der / 2;

				if(y == 0)
					der = (data[(y + 1) * ScanWidth + chans * x] - data[y * ScanWidth + chans * x]) / 2;
				else if(y == height - 1)
					der = (data[y * ScanWidth + chans * x] - data[(y - 1) * ScanWidth + chans * x]) / 2;
				else
					der = data[(y + 1) * ScanWidth + chans * x] - data[(y - 1) * ScanWidth + chans * x];
				data[y * ScanWidth + chans * x + 3] = 127 + der / 2;
			}

		for (int y = 0; y < height; y++)
			for (int x = 0; x < width; x++)
			{
				float min_ratio2, actual_ratio;
				int x1, x2, y1, y2;
				float ht, dhdx, dhdy, r2, h2;

				min_ratio2 = max_ratio * max_ratio;

				ht = data[y * ScanWidth + chans * x] / 255.0;
				dhdx = (data[y * ScanWidth + chans * x + 2] / 255.0 - 0.5) * width;
				dhdy = -(data[y * ScanWidth + chans * x + 3] / 255.0 - 0.5) * height;

				for (int rad = 1; (rad*rad <= 1.1 * 1.1 * (1.0 - ht)*(1.0 - ht) * min_ratio2 * width * height)
					&& (rad <= 1.1 * (1.0 - ht) * width) && (rad <= 1.1 * (1.0 - ht) * height); ++rad)
				{
					//west
					x1 = x - rad;
					while (x1 < 0)
						x1 += width;
					if (x1 >= 0)
					{
						float delx = -rad * iwidth;
						y1 = y - rad + 1;
						if (y1 < 0) y1 = 0;
						y2 = y + rad - 1;
						if (y2 >= height)
							y2 = height - 1;

						for (int dy = y1; dy <= y2; ++dy)
						{
							float dely = (dy - y)*iheight;
							r2 = delx * delx + dely *dely;
							h2 = data[dy * ScanWidth + chans * x1] / 255.0 - ht;
							if ((h2 > 0.0) && (h2 * h2 * min_ratio2 > r2))
							{
								min_ratio2 = r2 / (h2 * h2);
							}
						}
					}

					x2 = x + rad;
					while (x2 >= width)
						x2 -= width;
					if (x2 < width)
					{
						float delx = rad * iwidth;
						y1 = y - rad + 1;
						if (y1 < 0)
							y1 = 0;
						y2 = y + rad - 1;
						if (y2 >= height)
							y2 = height - 1;
						for (int dy = y1; dy <= y2; ++dy)
						{
							float dely = (dy - y) * iheight;
							r2 = delx * delx + dely * dely;
							h2 = data[dy * ScanWidth + chans * x2] / 255.0 - ht;
							if ((h2 > 0.0) && (h2 * h2 * min_ratio2 > r2))
							{
								min_ratio2 = r2 / (h2 * h2);
							}
						}
					}

					//North
					y1 = y - rad;
					while (y1 < 0)
						y1 += height;
					if (y1 >= 0)
					{
						float dely = -rad * iheight;
						x1 = x - rad;
						if (x1 < 0)
							x1 = 0;
						x2 = x + rad;
						if (x2 >= width)
							x2 = width - 1;
						for (int dx = x1; dx <= x2; ++dx)
						{
							float delx = (dx - x)* iwidth;
							r2 = delx *delx + dely *dely;
							h2 = data[y1 * ScanWidth + chans * dx] / 255.0 - ht;
							if ((h2 > 0.0) && (h2 * h2 * min_ratio2 > r2))
							{
								min_ratio2 = r2 / (h2 * h2);
							}
						}
					}

					//South
					y2 = y + rad;
					while (y2 >= height)
						y2 -= height;
					if (y2 < height)
					{
						float dely = rad * iheight;
						x1 = x - rad;
						if (x1 < 0)x1 = 0;
						x2 = x + rad;
						if (x2 >= width)
							x2 = width - 1;
						for (int dx = x1; dx < x2; ++dx)
						{
							float delx = (dx - x)*iwidth;
							r2 = delx * delx + dely *dely;
							h2 = data[y2 * ScanWidth + chans * dx] / 255.0 - ht;
							if ((h2 > 0.0) && (h2 * h2 * min_ratio2 > r2))
							{
								min_ratio2 = r2 / (h2*h2);
							}
						}
					}
				}
					actual_ratio = sqrt(min_ratio2);
					actual_ratio /= max_ratio;

					actual_ratio = sqrt(actual_ratio);

					data[y * ScanWidth + chans * x + 1] = static_cast<unsigned char>(255.0 * actual_ratio + 0.5);
					if (data[y * ScanWidth + chans * x + 1] < 1)
						data[y * ScanWidth + chans * x + 1] = 1;
			}

		glBindTexture(GL_TEXTURE_2D, textureID);
		glTexImage2D(GL_TEXTURE_2D, 0, format, width, height, 0, format, GL_UNSIGNED_BYTE, data);


		glTexParameteri(GL_TEXTURE_2D, GL_GENERATE_MIPMAP, GL_TRUE);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 0);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 7);
		glGenerateMipmap(GL_TEXTURE_2D);

		stbi_image_free(data);
	}
	else
	{
		stbi_image_free(data);
	}

	return textureID;
}